﻿using Analyzer.RouteService;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Analyzer.Models
{
    public class ReportModel
    {

        #region Private Properties

#error BingAppId must be defined in app.config
        /// <summary>
        /// Bing Maps Serivce Application ID. Used to authenticate Bing services
        /// </summary>
        private static string BING_APP_ID = ConfigurationSettings.AppSettings["BingAppId"];

        private Dictionary<BehaviorClassifications, double> _Deductions;
        #endregion

        #region Static Properties

        /// <summary>
        /// Minimum value at which longitudinal acceleration will be flagged 
        /// </summary>
        static public double LIMIT_ACCELERATION = 4.41;

        /// <summary>
        /// Maximum value at which longitudinal deacceleration will be flagged
        /// </summary>
        static public double LIMIT_DEACCELERATION = -4.76;

        /// <summary>
        /// Maximum value at which lateral acceleration will be flagged
        /// </summary>
        static public double LIMIT_LATERALACCELERATION = 6.0;

        static public double LIMIT_SPEED = 65.0;
        /// <summary>
        /// Number of datapoints considered in a speedlimit violation and roadway identification
        /// </summary>
        static public int INSTANCE_INTERVAL = 10;

        #endregion

        #region Public Properties

        /// <summary>
        /// Aggregated count of each Behavior flag
        /// </summary>
        public Dictionary<BehaviorClassifications, double> Summary { get; private set; }

        /// <summary>
        /// Amount of travel spent over speed limit.
        /// </summary>
        public float ExcessiveSpeed { get; set; }

        public double Points { get; private set; }

        public double HardTurnCount { get; set; }
        public double ExcessiveAccelerationCount { get; set; }
        public double SuddenBrakingCount { get; set; }
        public double SpeedingCount { get; set; }

        public double HardTurnDeductions { get; set; }
        public double ExcessiveAccelerationDeductions { get; set; }
        public double SuddenBrakingDeductions { get; set; }
        public double SpeedingDeductions { get; set; }

        public double HardTurnTotal = 0;
        public double ExcessiveAccelerationTotal = 0;
        public double SuddenBrakingTotal = 0;
        public double SpeedingTotal = 0;

        public bool CurrentlyHardTurning = false;
        public bool CurrentlyExcessivelyAccelerating = false;
        public bool CurrentlySuddenlyBraking = false;
        public bool CurrentlySpeeding = false;

        public int HardTurnCounter = 0;
        public int ExcessiveAccelerationCounter = 0;
        public int SuddenBrakingCounter = 0;
        public int SpeedingCounter = 0;

        /// <summary>
        /// List of aggressive behaviors that are flaggable
        /// </summary>
        public enum BehaviorClassifications
        {
            ExcessiveAcceleration = 2,
            HardTurn = 8,
            SuddenBraking = 4,
            Speeding = 1
        }

        /// <summary>
        /// Associates penalties with their respective behavior flags
        /// </summary>
        public Dictionary<BehaviorClassifications, double> Deductions
        {
            get
            {
                if (this._Deductions == null)
                {
                    _Deductions = new Dictionary<BehaviorClassifications, double>();
                    _Deductions[BehaviorClassifications.ExcessiveAcceleration] = -2;
                    _Deductions[BehaviorClassifications.SuddenBraking] = -2;
                    _Deductions[BehaviorClassifications.HardTurn] = -2;
                    _Deductions[BehaviorClassifications.Speeding] = -1.125;
                }

                return this._Deductions;
            }
        }

        /// <summary>
        /// Array of coordinates directly from raw data
        /// </summary>
        public List<Location> CoordinatesRaw;

        /// <summary>
        /// Array of coordinates snapped to nearest road from raw data
        /// </summary>
        public List<Location> CoordinatesMapped;

        public List<SnapshotModel> Snapshots { get; private set; }

        public string Grade
        {
            get
            {
                if (this.Points >= 97)
                {
                    return "A+";
                }
                else if (this.Points >= 94)
                {
                    return "A";
                }
                else if (this.Points >= 90)
                {
                    return "A-";
                }
                else if (this.Points >= 87)
                {
                    return "B+";
                }
                else if (this.Points >= 84)
                {
                    return "B";
                }
                else if (this.Points >= 80)
                {
                    return "B-";
                }
                else if (this.Points >= 77)
                {
                    return "C+";
                }
                else if (this.Points >= 74)
                {
                    return "C";
                }
                else if (this.Points >= 70)
                {
                    return "C-";
                }
                else if (this.Points >= 67)
                {
                    return "D+";
                }
                else if (this.Points >= 64)
                {
                    return "D";
                }
                else if (this.Points >= 60)
                {
                    return "D-";
                }
                else
                {
                    return "F";
                }
            }
        }

        /// <summary>
        /// Holds XML string for SVG image of Acceleration graph
        /// </summary>
        public string SVGPlotAcceleration { get; set; }

        /// <summary>
        /// Holds XML string for SVG image of Speed graph
        /// </summary>
        public string SVGPlotSpeed { get; set; }

        public string VIN { get; set; }

        #endregion

        #region Private Methods

        /// <summary>
        /// Snaps GPS coordinates to nearest road using Bing Routing Service
        /// </summary>
        /// <param name="coordinates">A list of GPS coordinates</param>
        private void ResolveCoordinates(List<Location> coordinates)
        {
            // Create the service variable.
            RouteServiceClient routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");

            // Set the token.
            RouteRequest routeRequest = new RouteRequest();
            routeRequest.Credentials = new RouteService.Credentials();
            routeRequest.Credentials.ApplicationId = new Microsoft.Maps.MapControl.WPF.ApplicationIdCredentialsProvider(BING_APP_ID).ApplicationId;

            // Return the route points so the route can be drawn.
            routeRequest.Options = new RouteOptions();
            routeRequest.Options.RoutePathType = RoutePathType.Points;
            routeRequest.Options.Mode = TravelMode.Driving;

            // Set the waypoints of the route to be calculated using the Geocode Service results stored in the geocodeResults variable.
            List<Waypoint> waypoints = new List<Waypoint>();

            int i = 0;
            foreach (Location point in coordinates)
            {
                i++;
                if (i < 10)
                    continue;

                if (point.Latitude == 0.0)
                    continue;

                if (point.Longitude == 0.0)
                    continue;

                RouteService.Waypoint waypoint = new Waypoint();
                waypoint.Location = point;

                waypoints.Add(waypoint);
                i = 0;
            }
            routeRequest.Waypoints = waypoints.ToArray();

            // Make the CalculateRoute request.
            RouteResponse routeResponse = routeService.CalculateRoute(routeRequest);

            // If the route calculate was a success and contains a route, then save our resolved points.
            if ((routeResponse.ResponseSummary.StatusCode == RouteService.ResponseStatusCode.Success) & (routeResponse.Result.Legs.Length != 0))
            {
                // Retrieve the route points that define the shape of the route.
                foreach (RouteService.Location point in routeResponse.Result.RoutePath.Points)
                {
                    CoordinatesMapped.Add(point);
                }
            }
        }

        #endregion

        public ReportModel()
        {
            CoordinatesMapped = new List<Location>();
            CoordinatesRaw = new List<Location>();
            Snapshots = new List<SnapshotModel>();
            Summary = new Dictionary<BehaviorClassifications, double>();

            this.Points = 100;
            this.ExcessiveSpeed = 0;
        }

        #region Public Methods

        /// <summary>
        /// Analyzes all existing data. Must be called before accessing most properties
        /// </summary>
        public void Analyze()
        {
            HardTurnCount = 0;
            ExcessiveAccelerationCount = 0;
            SuddenBrakingCount = 0;
            SpeedingCount = 0;

            HardTurnDeductions = 0;
            ExcessiveAccelerationDeductions = 0;
            SuddenBrakingDeductions = 0;
            SpeedingDeductions = 0;
            // This sort could help order the reports, if we decided to bring back the Entry value.
            this.Snapshots = this.Snapshots.OrderBy(x => x.Entry).ToList();
            for (int i = 0; i < this.Snapshots.Count(); i++)
            {
                SnapshotModel snapshot = this.Snapshots[i];
                snapshot = CalculateMagnitude(snapshot); // Calculate data such as magnitude, speed limits, etc... for this snapshot and generate flags
                CoordinatesRaw.Add(new Location() { Latitude = snapshot.Latitude, Longitude = snapshot.Longitude }); // Add our raw coordinates to the list to be mapped later during report analyzation
                this.Snapshots[i] = snapshot;
            }
            this.Points = this.Points - ExcessiveAccelerationTotal - SuddenBrakingTotal - HardTurnTotal - SpeedingTotal;
        }

        public SnapshotModel CalculateMagnitude(SnapshotModel snapshot)
        {
            double gForce = 9.8;

            double x = (snapshot.XAxis > 0) ? Math.Pow(Math.Abs(snapshot.XAxis), 2) : -(Math.Pow(Math.Abs(snapshot.XAxis), 2));
            double y = (snapshot.YAxis > 0) ? Math.Pow(Math.Abs(snapshot.YAxis), 2) : -(Math.Pow(Math.Abs(snapshot.YAxis), 2));
            double z = (snapshot.ZAxis > 0) ? Math.Pow(Math.Abs(snapshot.ZAxis + gForce), 2) : -(Math.Pow(Math.Abs(snapshot.ZAxis + gForce), 2));

            double acceleration = x + y + z;
            snapshot.Acceleration = (acceleration > 0) ? Math.Sqrt(Math.Abs(acceleration)) : -(Math.Sqrt(Math.Abs(acceleration)));

            if ((snapshot.YAxis * -1) > ReportModel.LIMIT_ACCELERATION && !CurrentlyExcessivelyAccelerating)
            {
                CurrentlyExcessivelyAccelerating = true;
                ExcessiveAccelerationDeductions += Math.Abs(snapshot.YAxis / ReportModel.LIMIT_ACCELERATION) - 1;
                ExcessiveAccelerationCounter++;
                // snapshot.Flags.Add(ReportModel.BehaviorClassifications.ExcessiveAcceleration);
            }
            else if ((snapshot.YAxis * -1) > ReportModel.LIMIT_ACCELERATION && CurrentlyExcessivelyAccelerating)
            {
                ExcessiveAccelerationDeductions += Math.Abs(snapshot.YAxis / ReportModel.LIMIT_ACCELERATION) - 1;
                ExcessiveAccelerationCounter++;
            }
            else if ((snapshot.YAxis * -1) <= ReportModel.LIMIT_ACCELERATION && CurrentlyExcessivelyAccelerating)
            {
                CurrentlyExcessivelyAccelerating = false;
                snapshot.Flags.Add(ReportModel.BehaviorClassifications.ExcessiveAcceleration);
                snapshot.ExcessiveAccelerationDeductions += ExcessiveAccelerationDeductions;
                snapshot.ExcessiveAccelerationCounter = ExcessiveAccelerationCounter;
                ExcessiveAccelerationTotal += ExcessiveAccelerationDeductions;
                ExcessiveAccelerationDeductions = 0;
                ExcessiveAccelerationCounter = 0;
                ExcessiveAccelerationCount++;
            }

            if ((snapshot.YAxis * -1) < ReportModel.LIMIT_DEACCELERATION && !CurrentlySuddenlyBraking)
            {
                CurrentlySuddenlyBraking = true;
                SuddenBrakingDeductions += Math.Abs(snapshot.YAxis / ReportModel.LIMIT_DEACCELERATION) - 1;
                SuddenBrakingCounter++;
                // snapshot.Flags.Add(ReportModel.BehaviorClassifications.ExcessiveAcceleration);
            }
            else if ((snapshot.YAxis * -1) < ReportModel.LIMIT_DEACCELERATION && CurrentlySuddenlyBraking)
            {
                SuddenBrakingDeductions += Math.Abs(snapshot.YAxis / ReportModel.LIMIT_DEACCELERATION) - 1;
                SuddenBrakingCounter++;
            }
            else if ((snapshot.YAxis * -1) >= ReportModel.LIMIT_DEACCELERATION && CurrentlySuddenlyBraking)
            {
                CurrentlySuddenlyBraking = false;
                snapshot.Flags.Add(ReportModel.BehaviorClassifications.SuddenBraking);
                snapshot.SuddenBrakingDeductions += SuddenBrakingDeductions;
                snapshot.SuddenBrakingCounter = SuddenBrakingCounter;
                SuddenBrakingTotal += SuddenBrakingDeductions;
                SuddenBrakingDeductions = 0;
                SuddenBrakingCounter = 0;
                SuddenBrakingCount++;
            }

            if (Math.Abs(snapshot.XAxis) > ReportModel.LIMIT_LATERALACCELERATION && !CurrentlyHardTurning)
            {
                CurrentlyHardTurning = true;
                HardTurnDeductions += Math.Abs(snapshot.XAxis) / ReportModel.LIMIT_LATERALACCELERATION - 1;
                HardTurnCounter++;
                // snapshot.Flags.Add(ReportModel.BehaviorClassifications.HardTurn);
            }
            else if (Math.Abs(snapshot.XAxis) > ReportModel.LIMIT_LATERALACCELERATION && CurrentlyHardTurning)
            {
                HardTurnDeductions += Math.Abs(snapshot.XAxis) / ReportModel.LIMIT_LATERALACCELERATION - 1;
                HardTurnCounter++;
            }
            else if (Math.Abs(snapshot.XAxis) <= ReportModel.LIMIT_LATERALACCELERATION && CurrentlyHardTurning)
            {
                CurrentlyHardTurning = false;
                snapshot.Flags.Add(ReportModel.BehaviorClassifications.HardTurn);
                snapshot.HardTurnDeductions += HardTurnDeductions;
                snapshot.HardTurnCounter = HardTurnCounter;
                HardTurnTotal += HardTurnDeductions;
                HardTurnDeductions = 0;
                HardTurnCounter = 0;
                HardTurnCount++;
            }

            if (snapshot.Speed > ReportModel.LIMIT_SPEED && !CurrentlySpeeding)
            {
                CurrentlySpeeding = true;
                SpeedingDeductions += (snapshot.Speed / ReportModel.LIMIT_SPEED - 1) / 6;
                SpeedingCounter++;
                // snapshot.Flags.Add(ReportModel.BehaviorClassifications.Speeding);
            }
            else if (snapshot.Speed > ReportModel.LIMIT_SPEED && CurrentlySpeeding)
            {
                SpeedingDeductions += (snapshot.Speed / ReportModel.LIMIT_SPEED - 1) / 6;
                SpeedingCounter++;
            }
            else if (snapshot.Speed <= ReportModel.LIMIT_SPEED && CurrentlySpeeding)
            {
                CurrentlySpeeding = false;
                snapshot.Flags.Add(ReportModel.BehaviorClassifications.Speeding);
                snapshot.SpeedingDeductions += SpeedingDeductions;
                snapshot.SpeedingCounter = SpeedingCounter;
                SpeedingTotal += SpeedingDeductions;
                SpeedingDeductions = 0;
                SpeedingCounter = 0;
                SpeedingCount++;
            }

            return snapshot;
        }

        public SnapshotModel AddSnapshot()
        {
            SnapshotModel _snapshot = new SnapshotModel(this);
            _snapshot.Entry = this.Snapshots.Count + 1;

            this.Snapshots.Add(_snapshot);

            return _snapshot;
        }

        public string StringifySummary()
        {
            // Array to keep track of which infractions have been found.  The ordering is Speeding (0), Excessive Acceleration (1), Sudden Braking (2), and High Speed Turning (3)
            int[] arr = { 0, 0, 0, 0 };

            foreach (BehaviorClassifications behavior in this.Summary.Keys)
            {
                switch (behavior)
                {
                    case BehaviorClassifications.ExcessiveAcceleration:
                        arr[1] = (int)behavior;
                        break;
                    case BehaviorClassifications.HardTurn:
                        arr[3] = (int)behavior;
                        break;

                    case BehaviorClassifications.Speeding:
                        arr[0] = (int)behavior;
                        break;

                    case BehaviorClassifications.SuddenBraking:
                        arr[2] = (int)behavior;
                        break;
                }
            }

            int mask = arr[0] + arr[1] + arr[2] + arr[3];

            return mask.ToString();

            /*
            List<string> infractions = new List<string>();

            foreach (var Behavior in this.Summary.Keys)
            {
                infractions.Add(Behavior.ToString());
            }

            return Newtonsoft.Json.JsonConvert.SerializeObject(infractions);
             * */
        }

        #endregion



        public class SnapshotModel
        {
            private ReportModel Report;

            #region Public Properties

            /// <summary>
            /// Total of speed traveled over the limit in percentage
            /// </summary>
            public float SpeedOver { get; private set; }

            public int Entry;
            public double Acceleration { get; set; }
            public double Heading { get; set; }
            public double Latitude { get; set; }
            public double Longitude { get; set; }
            public double Fuel { get; set; }
            public double Speed { get; set; }
            public double SpeedLimit { get; set; }
            public double Throttle { get; set; }
            public double XAxis { get; set; }
            public double YAxis { get; set; }
            public double ZAxis { get; set; }
            public string VIN { get; set; }
            public double HardTurnDeductions = 0;
            public double ExcessiveAccelerationDeductions = 0;
            public double SuddenBrakingDeductions = 0;
            public double SpeedingDeductions = 0;
            public int HardTurnCounter = 0;
            public int ExcessiveAccelerationCounter = 0;
            public int SuddenBrakingCounter = 0;
            public int SpeedingCounter = 0;
            public List<ReportModel.BehaviorClassifications> Flags { get; private set; }
            //public RouteTags[] WaypointTags { get; private set; }

            #endregion

            public SnapshotModel(ReportModel _report)
            {
                this.Report = _report;
                this.Flags = new List<ReportModel.BehaviorClassifications>();
                this.SpeedLimit = 65.0;
            }

            #region Private Methods

            /// <summary>
            /// Calculates the magnitude of the triaxial acceleration values
            /// </summary>
            private void CalculateMagnitude()
            {
                double gForce = 9.8;

                double x = (this.XAxis > 0) ? Math.Pow(Math.Abs(this.XAxis), 2) : -(Math.Pow(Math.Abs(this.XAxis), 2));
                double y = (this.YAxis > 0) ? Math.Pow(Math.Abs(this.YAxis), 2) : -(Math.Pow(Math.Abs(this.YAxis), 2));
                double z = (this.ZAxis > 0) ? Math.Pow(Math.Abs(this.ZAxis + gForce), 2) : -(Math.Pow(Math.Abs(this.ZAxis + gForce), 2));

                double acceleration = x + y + z;
                this.Acceleration = (acceleration > 0) ? Math.Sqrt(Math.Abs(acceleration)) : -(Math.Sqrt(Math.Abs(acceleration)));

                if (this.YAxis > ReportModel.LIMIT_ACCELERATION)
                {
                    this.Flags.Add(ReportModel.BehaviorClassifications.ExcessiveAcceleration);
                }
                else if (this.YAxis < ReportModel.LIMIT_DEACCELERATION)
                {
                    this.Flags.Add(ReportModel.BehaviorClassifications.SuddenBraking);
                }

                if (Math.Abs(this.XAxis) > ReportModel.LIMIT_LATERALACCELERATION)
                {
                    this.Flags.Add(ReportModel.BehaviorClassifications.HardTurn);
                }
            }

            /// <summary>
            /// Fetches tags for associated road segments, breaks route into 5 second subroutes
            /// </summary>
            /*
            private void FetchWaypointDetails(int interval)
            {
                /// Every [interval] seconds create a subroute within the major route and grab details on the subroute 
                if ((this.Entry % interval) != 0 || this.Entry == 0)
                    return;

                string connection_string = "Server=localhost;Port=5432;User Id=postgres;Password=!!abc123; Database=postgis;";

                OsmSharp.Osm.Data.PostgreSQL.PostgreSQLSnapshotSource source = new OsmSharp.Osm.Data.PostgreSQL.PostgreSQLSnapshotSource(connection_string);
                // keeps a memory-efficient version of the osm-tags.
                OsmTagsIndex tags_index = new OsmTagsIndex();
                // creates a routing interpreter. (used to translate osm-tags into a routable network)
                OsmRoutingInterpreter interpreter = new OsmRoutingInterpreter();
                // create routing inter
                OsmSourceRouterDataSource routing_data = new OsmSourceRouterDataSource(interpreter, tags_index, source, VehicleEnum.Car);
                // create the router object.
                IRouter<RouterPoint> router = new Router<PreProcessedEdge>(routing_data, interpreter, new DykstraRoutingPreProcessed(routing_data.TagsIndex));

                SnapshotModel start = Report.Snapshots[this.Entry - interval];

                GeoCoordinate startPoint = new GeoCoordinate(start.Latitude, start.Longitude);
                GeoCoordinate endPoint = new GeoCoordinate(this.Latitude, this.Longitude);
                GeoCoordinate[] waypoints = { startPoint, endPoint };

                RouterPoint[] routePoints = router.Resolve(VehicleEnum.Car, waypoints);

                RouterTSP tsp_solver = new RouterTSPAEXGenetic();
                OsmSharp.Routing.TSP.RouterTSPWrapper<RouterPoint, RouterTSP> tsp_router = new RouterTSPWrapper<RouterPoint, RouterTSP>(tsp_solver, router);
                OsmSharpRoute route = tsp_router.CalculateTSP(VehicleEnum.Car, routePoints, true);

                ///Loop through each tag associated with a particular route
                foreach (RoutePointEntry entry in route.Entries)
                {
                    if (entry.Points == null)
                        continue;

                    RouteTags[] routeTags = { };
                    if (entry.Tags != null)
                        if (entry.Tags.Length > 0)
                            routeTags = entry.Tags;

                    ///Loop through each tag associated with a waypoint in the route
                    foreach (RoutePoint routePoint in entry.Points)
                    {
                        #region Determine Speed Limit for Waypoint
                        /// Default base speed limit
                        double limit = 65.0;

                        if (routePoint.Tags != null)
                            if (routePoint.Tags.Length > 0)
                                Debug.WriteLine("route point has " + routePoint.Tags.Length + " tags");

                        string type = null;
                        double maxspeed = -1;

                        #region Road Classification
                        this.WaypointTags = routePoint.Tags;
                        foreach (RouteTags tag in routeTags)
                        {
                            if (tag.Key.Equals("highway"))
                            {
                                type = tag.Value;
                                Debug.Print("Entry point is a " + type);
                            }
                        }

                        #endregion

                        #region Max Speed Tags
                        foreach (RouteTags tag in routeTags)
                        {
                            if (tag.Key.Equals("maxspeed"))
                            {
                                maxspeed = double.Parse(tag.Value);
                                Debug.Print("Maximum speed is " + maxspeed.ToString());
                                switch (tag.Value)
                                {
                                    case "none":
                                        {
                                            maxspeed = limit;
                                            break;
                                        }
                                    case "walk":
                                        {
                                            maxspeed = limit;
                                            break;
                                        }
                                    case "signals":
                                        {
                                            maxspeed = limit;
                                            break;
                                        }
                                    default:
                                        {
                                            Regex rgx = new Regex("[^0-9]");
                                            string raw = rgx.Replace(tag.Value, "");
                                            maxspeed = double.Parse(raw);
                                            break;
                                        }
                                }
                            }
                        }

                        #endregion

                        if (maxspeed != -1)
                        {
                            limit = maxspeed;
                        }
                        else if (type != null)
                        {
                            switch (type)
                            {
                                case "residential":
                                    {
                                        limit = 35.0;
                                        break;
                                    }
                                case "living_street":
                                    {
                                        limit = 25.0;
                                        break;
                                    }
                                case "secondary":
                                    {
                                        limit = 55.0;
                                        break;
                                    }
                            }
                        }

                        for (int i = (this.Entry - interval); i < this.Entry; i++)
                        {
                            Report.Snapshots[i].SpeedLimit = limit;
                        }

                        #endregion
                    }
                }
            }
            */
            private void CalculateSpeedLimit(int interval)
            {
                /// Set speed limits for past [interval] values calculated in waypoint method
                int index = this.Report.Snapshots.IndexOf(this);
                if ((index % interval) != 0 || index == 0)
                    return;

                float speedRatio = (float)((this.Speed / this.SpeedLimit) - 1);

                for (int i = (index - interval); i < index; i++)
                {
                    if (speedRatio > 0)
                    {
                        Report.Snapshots[i].SpeedOver = speedRatio;
                        Report.ExcessiveSpeed = (Report.ExcessiveSpeed + speedRatio);
                    }
                }
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// Calculates values for this snapshot
            /// </summary>
            public void Calculate()
            {
                this.CalculateMagnitude();
                //this.FetchWaypointDetails(10);
                // this.CalculateSpeedLimit(ReportModel.INSTANCE_INTERVAL);
            }

            #endregion
        }
    }
}
